package com.codeanalysissys.backend.ruby;
import ghaffarian.graphs.Edge;
import ghaffarian.nanologger.Logger;
import com.codeanalysissys.backend.graphs.cfg.CFEdge;
import com.codeanalysissys.backend.graphs.cfg.CFNode;

import com.codeanalysissys.backend.graphs.pdg.CDEdge;
import com.codeanalysissys.backend.graphs.pdg.PDNode;
import com.codeanalysissys.backend.graphs.pdg.ControlDependenceGraph;

import com.codeanalysissys.backend.ruby.parser.RubyParserBaseVisitor;
import com.codeanalysissys.backend.ruby.parser.RubyParserVisitor;

import com.codeanalysissys.backend.ruby.parser.RubyLexer;
import com.codeanalysissys.backend.ruby.parser.RubyParser;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.Deque;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
public class RubyCDGBuilder {
    public static ControlDependenceGraph build(InputStream inFile) throws IOException {
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        RubyLexer lexer = new RubyLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        RubyParser parser = new RubyParser(tokens);
        ParseTree tree = parser.compilation_unit();
        ControlDependenceGraph cdg = new ControlDependenceGraph();
        ControlDependencyVisitor visitor = new ControlDependencyVisitor(cdg);
        visitor.visit(tree);
        return cdg;
    }

    public static ControlDependenceGraph build(File rubyFile) throws IOException {
        if (!rubyFile.getName().endsWith(".rb"))
            throw new IOException("Not a ruby File!");
        CharStream input = CharStreams.fromPath(Paths.get(rubyFile.getAbsolutePath()));
        RubyLexer lexer = new RubyLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        RubyParser parser = new RubyParser(tokens);
        ParseTree tree = parser.compilation_unit();
        Logger.debug("CTRL DEP ANALYSIS: " + rubyFile.getPath());
        ControlDependenceGraph cdg = new ControlDependenceGraph(rubyFile.getName());
        ControlDependencyVisitor visitor = new ControlDependencyVisitor(cdg);
        visitor.visit(tree);
        return cdg;
    }
    private static class ControlDependencyVisitor extends RubyParserBaseVisitor<PDNode> {
        private PDNode root;
        private Deque<PDNode>nowclass=new ArrayDeque<>();
        private PDNode nowif;
        private PDNode  methodentry;
        private PDNode nowthen;
        private PDNode nowelse;
        private CDEdge.Type type;
        private int num;
        private int test=0;
        private ControlDependenceGraph cdg;
        private Deque<PDNode> ctrlDeps;
        private Deque<PDNode> negDeps;
        private Deque<PDNode> classentry;
        private Deque<Integer> jmpCounts;
        private Deque<PDNode> jumpDeps;

        private Deque<String> classNames;
        private boolean isfor;
        private boolean buildRegion;
        private boolean follows;
        private boolean build;
        private boolean Done;
        private String expr;
        private Deque<PDNode> BREAK;
        private Deque<PDNode>  NEXT;
        private Deque<PDNode> RETURN;
        private int end;
        private int lastFollowDepth;
        private int regionCounter;
        private int jmpCounter;


        public ControlDependencyVisitor(ControlDependenceGraph cdg) {
            num=0;
            this.cdg = cdg;
            classentry=new ArrayDeque<>();
            ctrlDeps = new ArrayDeque<>();
            negDeps = new ArrayDeque<>();
            jumpDeps = new ArrayDeque<>();
            jmpCounts = new ArrayDeque<>();
            classNames=new ArrayDeque<>();
            isfor=false;
            buildRegion = false;
            follows = true;
            end=0;
            lastFollowDepth = 0;
            regionCounter = 1;
            jmpCounter = 0;
            Done=false;
            expr="";
            root=new PDNode();
            nowif=null;
            methodentry=null;
            build =false;
            root.setCode("root");
            root.setLineOfCode(0);
            pushCtrlDep(root);
            cdg.addVertex(root);

            BREAK=new ArrayDeque<>();
            RETURN=new ArrayDeque<>();
            NEXT=new ArrayDeque<>();
        }
        private void init() {
            isfor=false;

            ctrlDeps.clear();
            pushCtrlDep(root);
            negDeps.clear();
            jumpDeps.clear();
            jmpCounts.clear();
            buildRegion = false;
            follows = true;
            end=0;
            lastFollowDepth = 0;
            regionCounter = 1;
            jmpCounter = 0;
            Done=false;
            expr="";
            nowif=null;
            methodentry=null;
            build=false;
            BREAK.clear();
            RETURN.clear();
            NEXT.clear();
        }
        @Override
        public PDNode visitExprBlock(RubyParser.ExprBlockContext ctx)
        {
            PDNode ifnode=new PDNode();
            ifnode.setLineOfCode(ctx.getStart().getLine());
            ifnode.setCode(getOriginalCodeText(ctx.expr()));
            addNodeEdge(ifnode);
            PDNode thenRegion = new PDNode();
            thenRegion.setLineOfCode(0);
            thenRegion.setCode("LOOP");
            cdg.addVertex(thenRegion);
            cdg.addEdge(new Edge<>(ifnode, new CDEdge(CDEdge.Type.TRUE), thenRegion));
            if(ctx.block().block_params()!=null);
            {
                PDNode params=new PDNode();
                params.setLineOfCode(ctx.block().block_params().getStart().getLine());
                params.setCode(getOriginalCodeText(ctx.block().block_params()));
                addNodeEdge(params);
            }
            visit(ctx.block().statement_list_terms());
            expNode(ctx.block().statement_list_terms());
            popCtrlDep(thenRegion);
            if(!BREAK.isEmpty()) {
                for(int i=0;i<BREAK.size();i++)
                    popCtrlDep(BREAK.pop());
            }
            if(!NEXT.isEmpty())
            {
                for(int i=0;i<NEXT.size();i++)
                    popCtrlDep(NEXT.pop());
            }
            isfor=false;
            Done=true;
            return null;
        }
        @Override
        public PDNode visitExprDotRef(RubyParser.ExprDotRefContext ctx)
        {
            isfor=true;
            return null;
        }
        @Override
        public PDNode visitExprWitStatementSuffix(RubyParser.ExprWitStatementSuffixContext ctx)
        {
            if(ctx.expr_statement_suffix().DO()!=null) {
                visit(ctx.expr());
                if(isfor)
                {
                    PDNode ifnode=new PDNode();
                    ifnode.setLineOfCode(ctx.getStart().getLine());
                    ifnode.setCode(getOriginalCodeText(ctx.expr()));
                    addNodeEdge(ifnode);
                    PDNode thenRegion = new PDNode();
                    thenRegion.setLineOfCode(0);
                    thenRegion.setCode("LOOP");
                    cdg.addVertex(thenRegion);
                    cdg.addEdge(new Edge<>(ifnode, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                    pushCtrlDep(thenRegion);
                    if(ctx.expr_statement_suffix().block_params()!=null);
                    {
                        PDNode params=new PDNode();
                        params.setLineOfCode(ctx.expr_statement_suffix().block_params().getStart().getLine());
                        params.setCode(getOriginalCodeText(ctx.expr_statement_suffix().block_params()));
                        addNodeEdge(params);
                    }
                    visit(ctx.expr_statement_suffix().statement_list_terms());
                    expNode(ctx.expr_statement_suffix().statement_list_terms());
                    popCtrlDep(thenRegion);
                    if(!BREAK.isEmpty()) {
                        for(int i=0;i<BREAK.size();i++)
                            popCtrlDep(BREAK.pop());
                    }
                    if(!NEXT.isEmpty())
                    {
                        for(int i=0;i<NEXT.size();i++)
                            popCtrlDep(NEXT.pop());
                    }
                    isfor=false;
                }
            }
            else if(ctx.expr_statement_suffix().UNTIL()!=null)
            {
                PDNode ifnode=new PDNode();
                ifnode.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                ifnode.setCode(getOriginalCodeText(ctx.expr_statement_suffix()));
                addNodeEdge(ifnode);
                PDNode thenRegion = new PDNode();
                thenRegion.setLineOfCode(0);
                thenRegion.setCode("LOOP");
                cdg.addVertex(thenRegion);
                cdg.addEdge(new Edge<>(ifnode, new CDEdge(CDEdge.Type.FALSE), thenRegion));
                PDNode exp=new PDNode();
                exp.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                if(!expr.equals("")) {
                    exp.setCode(expr + " " + getOriginalCodeText(ctx.expr()));
                    expr="";
                }
                else
                    exp.setCode(getOriginalCodeText(ctx.expr()));
                cdg.addVertex(exp);
                cdg.addEdge(new Edge<>(thenRegion, new CDEdge(CDEdge.Type.EPSILON), exp));
            }
            else if(ctx.expr_statement_suffix().WHILE()!=null)
            {
                PDNode ifnode=new PDNode();
                ifnode.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                ifnode.setCode(getOriginalCodeText(ctx.expr_statement_suffix()));
                addNodeEdge(ifnode);
                PDNode thenRegion = new PDNode();
                thenRegion.setLineOfCode(0);
                thenRegion.setCode("LOOP");
                cdg.addVertex(thenRegion);
                cdg.addEdge(new Edge<>(ifnode, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                PDNode exp=new PDNode();
                exp.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                if(!expr.equals("")) {
                    exp.setCode(expr + " " + getOriginalCodeText(ctx.expr()));
                    expr="";
                }
                else
                    exp.setCode(getOriginalCodeText(ctx.expr()));
                cdg.addVertex(exp);
                cdg.addEdge(new Edge<>(thenRegion, new CDEdge(CDEdge.Type.EPSILON), exp));
            }
            else if(ctx.expr_statement_suffix().IF()!=null)
            {
                PDNode ifnode=new PDNode();
                ifnode.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                ifnode.setCode(getOriginalCodeText(ctx.expr_statement_suffix()));
                addNodeEdge(ifnode);
                nowif=ifnode;

                PDNode thenRegion = new PDNode();
                thenRegion.setLineOfCode(0);
                thenRegion.setCode("THEN");
                cdg.addVertex(thenRegion);
                cdg.addEdge(new Edge<>(ifnode, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                nowthen=thenRegion;
                nowelse=null;
                type=CDEdge.Type.FALSE;
                PDNode exp=new PDNode();
                exp.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                if(!expr.equals("")) {
                    exp.setCode(expr + " " + getOriginalCodeText(ctx.expr()));
                    expr="";
                }
                else {
                    pushCtrlDep(thenRegion);
                    visit(ctx.expr());//检查expr里面有没有跳转语句
                    popCtrlDep(thenRegion);
                    if(Done!=true)
                        exp.setCode(getOriginalCodeText(ctx.expr()));
                    else
                    {
                        nowif=null;
                        nowelse=null;
                        nowthen=null;
                        Done=true;
                        return null;
                    }

                }
                cdg.addVertex(exp);
                cdg.addEdge(new Edge<>(thenRegion, new CDEdge(CDEdge.Type.EPSILON), exp));
                nowif=null;
                nowthen=null;
            }
            else if(ctx.expr_statement_suffix().UNLESS()!=null)
            {
                PDNode ifnode=new PDNode();
                ifnode.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                ifnode.setCode(getOriginalCodeText(ctx.expr_statement_suffix()));
                addNodeEdge(ifnode);
                nowif=ifnode;
                PDNode thenRegion = new PDNode();
                thenRegion.setLineOfCode(0);
                thenRegion.setCode("THEN");
                cdg.addVertex(thenRegion);
                cdg.addEdge(new Edge<>(ifnode, new CDEdge(CDEdge.Type.FALSE), thenRegion));
                nowthen=thenRegion;
                nowelse=null;
                type=CDEdge.Type.TRUE;
                PDNode exp=new PDNode();
                exp.setLineOfCode(ctx.expr_statement_suffix().getStart().getLine());
                if(!expr.equals("")) {
                    exp.setCode(expr + " " + getOriginalCodeText(ctx.expr()));
                    expr="";
                }
                else {
                    pushCtrlDep(thenRegion);
                    visit(ctx.expr());//检查expr里面有没有跳转语句
                    popCtrlDep(thenRegion);

                    if(Done!=true)
                        exp.setCode(getOriginalCodeText(ctx.expr()));
                    else
                    {
                        nowif=null;
                        nowelse=null;
                        nowthen=null;
                        Done=true;
                        return null;
                    }
                }
                cdg.addVertex(exp);
                cdg.addEdge(new Edge<>(thenRegion, new CDEdge(CDEdge.Type.EPSILON), exp));
                nowif=null;
                nowthen=null;
            }
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryBlockUntil(RubyParser.PrimaryBlockUntilContext ctx)
        {
            PDNode FOR=new PDNode();
            FOR.setLineOfCode(ctx.getStart().getLine());
            String whileexp="until "+getOriginalCodeText(ctx.expr());
            if(ctx.do_keyword().SEMICOLON()!=null)
                whileexp+=" ;";
            if(ctx.do_keyword().DO()!=null)
                whileexp+=" do";
            FOR.setCode(whileexp);
            addNodeEdge(FOR);
            PDNode loopRegion = new PDNode();
            loopRegion.setLineOfCode(0);
            loopRegion.setCode("LOOP");
            cdg.addVertex(loopRegion);
            cdg.addEdge(new Edge<>(FOR, new CDEdge(CDEdge.Type.FALSE), loopRegion));
            pushCtrlDep(loopRegion);
            visit(ctx.statement_list_terms());
            expNode(ctx.statement_list_terms());
            popCtrlDep(loopRegion);
            if(!BREAK.isEmpty()) {
                for(int i=0;i<BREAK.size();i++)
                    popCtrlDep(BREAK.pop());
            }
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryBlockWhile(RubyParser.PrimaryBlockWhileContext ctx)
        {
            PDNode FOR=new PDNode();
            FOR.setLineOfCode(ctx.getStart().getLine());
            String whileexp="while "+getOriginalCodeText(ctx.expr());
            if(ctx.do_keyword().SEMICOLON()!=null)
                whileexp+=" ;";
            if(ctx.do_keyword().DO()!=null)
                whileexp+=" do";
            FOR.setCode(whileexp);
            addNodeEdge(FOR);
            PDNode loopRegion = new PDNode();
            loopRegion.setLineOfCode(0);
            loopRegion.setCode("LOOP");
            cdg.addVertex(loopRegion);
            cdg.addEdge(new Edge<>(FOR, new CDEdge(CDEdge.Type.TRUE), loopRegion));
            pushCtrlDep(loopRegion);
            visit(ctx.statement_list_terms());
            expNode(ctx.statement_list_terms());
            popCtrlDep(loopRegion);
            if(!BREAK.isEmpty()) {
                for(int i=0;i<BREAK.size();i++)
                    popCtrlDep(BREAK.pop());
            }
            if(!NEXT.isEmpty())
            {
                for(int i=0;i<NEXT.size();i++)
                    popCtrlDep(NEXT.pop());
            }
            Done=true;
            return null;
        }

        @Override
        public PDNode visitPrimaryBlockFor(RubyParser.PrimaryBlockForContext ctx)
        {
            PDNode FOR=new PDNode();
            FOR.setLineOfCode(ctx.getStart().getLine());
            FOR.setCode("for "+getOriginalCodeText(ctx.expr())+" in "+getOriginalCodeText(ctx.when_cond()));
            addNodeEdge(FOR);
            PDNode loopRegion = new PDNode();
            loopRegion.setLineOfCode(0);
            loopRegion.setCode("LOOP");
            cdg.addVertex(loopRegion);
            cdg.addEdge(new Edge<>(FOR, new CDEdge(CDEdge.Type.TRUE), loopRegion));
            pushCtrlDep(loopRegion);
            visit(ctx.statement_list_terms());
            expNode(ctx.statement_list_terms());
            popCtrlDep(loopRegion);
            if(!BREAK.isEmpty()) {
                for(int i=0;i<BREAK.size();i++)
                    popCtrlDep(BREAK.pop());
            }
            if(!NEXT.isEmpty())
            {
                for(int i=0;i<NEXT.size();i++)
                    popCtrlDep(NEXT.pop());
            }
            Done=true;
            return null;
        }
        @Override
        public PDNode visitCase_body(RubyParser.Case_bodyContext ctx)
        {
            if(ctx.WHEN()!=null)
            {
                PDNode when =new PDNode();
                when.setLineOfCode(ctx.getStart().getLine());
                String whenexp="when "+getOriginalCodeText(ctx.when_cond(0));
                if(ctx.then_keyword().SEMICOLON()!=null)
                    whenexp+=" ;";
                if(ctx.then_keyword().THEN()!=null)
                    whenexp+=" then";
                when.setCode(whenexp);
                addNodeEdge(when);
                pushCtrlDep(when);
                PDNode thenRegion = new PDNode();
                thenRegion.setLineOfCode(0);
                thenRegion.setCode("THEN");
                cdg.addVertex(thenRegion);
                cdg.addEdge(new Edge<>(when, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                pushCtrlDep(thenRegion);
                if(ctx.statement_list_terms()!=null) {
                    visit(ctx.statement_list_terms());
                    expNode(ctx.statement_list_terms());
                }
                popCtrlDep(thenRegion);

                return when;
            }
            else {
                visit(ctx.else_tail().statement_list_terms());
                expNode(ctx.else_tail().statement_list_terms());
                return null;
            }

        }
        @Override
        public  PDNode visitPrimaryBlockCase1(RubyParser.PrimaryBlockCase1Context ctx)
        {
            PDNode caseNode = new PDNode();
            caseNode.setLineOfCode(ctx.getStart().getLine());
            if(ctx.statement_list_terms().statement()!=null)
                if(ctx.DOLLAR()!=null)
                    caseNode.setCode("case $"+getOriginalCodeText(ctx.statement_list_terms().statement()));
                else
                    caseNode.setCode("case "+getOriginalCodeText(ctx.statement_list_terms().statement()));
            else
                caseNode.setCode("case");
            addNodeEdge(caseNode);
            pushCtrlDep(caseNode);
            int num=0;
            for(int i=0;i<ctx.case_body().size();i++)
            {
                visit(ctx.case_body(i));
                if(ctx.case_body(i).WHEN()!=null)
                {
                    num++;
                    PDNode els=new PDNode();
                    els.setLineOfCode(ctx.case_body(i+1).getStart().getLine());
                    els.setCode("ELSE");
                    cdg.addVertex(els);
                    cdg.addEdge(new Edge<>(ctrlDeps.peek(), new CDEdge(CDEdge.Type.FALSE), els));
                    pushCtrlDep(els);
                }

            }
            for(;num>0;num--)
            {
                ctrlDeps.pop();
                ctrlDeps.pop();
            }
            Done=true;
            popCtrlDep(caseNode);
            return null;
        }
        @Override
        public PDNode visitIf_tail(RubyParser.If_tailContext ctx)
        {

//                PDNode elseRegion = new PDNode();
//                elseRegion.setCode("ELSE");
//                cdg.addVertex(elseRegion);
//                cdg.addEdge(new Edge<>(ctrlDeps.pop(), new CDEdge(CDEdge.Type.FALSE), elseRegion));
                if (ctx.ELSIF() != null) {
                    PDNode elseif = new PDNode();
                    elseif.setLineOfCode(ctx.getStart().getLine());
                    String elsif = "if " + getOriginalCodeText(ctx.expr());
                    if (ctx.then_keyword().SEMICOLON() != null)
                        elsif += " :";
                    if (ctx.then_keyword().THEN() != null)
                        elsif += " then";
                    elseif.setCode(elsif);
                    addNodeEdge(elseif);
                    popCtrlDep(nowelse);
                    nowif = elseif;
                    popCtrlDep(nowif);
                    PDNode thenRegion = new PDNode();
                    thenRegion.setLineOfCode(0);
                    thenRegion.setCode("THEN");
                    cdg.addVertex(thenRegion);
                    cdg.addEdge(new Edge<>(elseif, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                    nowthen=thenRegion;
                    nowelse=null;
                    pushCtrlDep(thenRegion);
                    if (ctx.statement_list_terms() != null) {
                        visit(ctx.statement_list_terms());
                        expNode(ctx.statement_list_terms());
                    }
                    popCtrlDep(thenRegion);
                } else {

                    if (ctx.else_tail().statement_list_terms() != null) {
                        visit(ctx.else_tail().statement_list_terms());
                        expNode(ctx.else_tail().statement_list_terms());
                    }
                    popCtrlDep(nowelse);
                    nowthen=null;
                    nowelse=null;
                }


            return null;

        }

        @Override
        public PDNode visitPrimaryBlockIf(RubyParser.PrimaryBlockIfContext ctx)
        {
            PDNode ifNode = new PDNode();
            ifNode.setLineOfCode(ctx.getStart().getLine());
            String ifexpr="if "+getOriginalCodeText(ctx.expr());
            if(ctx.then_keyword().SEMICOLON()!=null)
                ifexpr+=" :";
            if(ctx.then_keyword().THEN()!=null)
                ifexpr+=" then";
            ifNode.setCode(ifexpr);
            nowif=ifNode;
            addNodeEdge(ifNode);

            pushCtrlDep(ifNode);
            PDNode thenRegion = new PDNode();
            thenRegion.setLineOfCode(0);
            thenRegion.setCode("THEN");
            cdg.addVertex(thenRegion);
            cdg.addEdge(new Edge<>(ifNode, new CDEdge(CDEdge.Type.TRUE), thenRegion));
            nowthen=thenRegion;
            nowelse=null;
            type=CDEdge.Type.FALSE;
            pushCtrlDep(thenRegion);
            if(ctx.statement_list_terms()!=null) {
                visit(ctx.statement_list_terms());
                expNode(ctx.statement_list_terms());
            }
            popCtrlDep(thenRegion);
            nowif=ifNode;
            if(!ctx.if_tail().isEmpty())
            {
                for ( int i = 0; i < ctx.if_tail().size(); i++) {
                    if(!build) {
                        PDNode elseRegion = new PDNode();
                        elseRegion.setCode("ELSE");
                        elseRegion.setLineOfCode(test);
                        test++;
                        cdg.addVertex(elseRegion);
                        cdg.addEdge(new Edge<>(nowif, new CDEdge(CDEdge.Type.FALSE), elseRegion));
                        popCtrlDep(nowif);
                        nowelse = elseRegion;
                        pushCtrlDep(nowelse);
                    }
                    else
                    {
                        popCtrlDep(nowif);
                        build =false;
                    }
                    visit(ctx.if_tail(i));
                }

                Done=true;
                return null;
            }
            nowif=null;
            popCtrlDep(ifNode);
            Done=true;
            return null;

        }
        @Override
        public PDNode visitPrimaryBlockUnless(RubyParser.PrimaryBlockUnlessContext ctx)
        {
            PDNode ifNode = new PDNode();
            ifNode.setLineOfCode(ctx.getStart().getLine());
            String ifexpr="unless "+getOriginalCodeText(ctx.expr());
            if(ctx.then_keyword().SEMICOLON()!=null)
                ifexpr+=" :";
            if(ctx.then_keyword().THEN()!=null)
                ifexpr+=" then";
            ifNode.setCode(ifexpr);
            nowif=ifNode;
            addNodeEdge(ifNode);

            pushCtrlDep(ifNode);
            PDNode thenRegion = new PDNode();
            thenRegion.setLineOfCode(0);
            thenRegion.setCode("THEN");
            cdg.addVertex(thenRegion);
            cdg.addEdge(new Edge<>(ifNode, new CDEdge(CDEdge.Type.FALSE), thenRegion));
            nowthen=thenRegion;
            nowelse=null;
            type=CDEdge.Type.TRUE;
            pushCtrlDep(thenRegion);
            if(ctx.statement_list_terms()!=null) {
                visit(ctx.statement_list_terms());
                expNode(ctx.statement_list_terms());
            }
            popCtrlDep(thenRegion);
            nowif=ifNode;
            if(ctx.else_tail()!=null)
            {
                if(!build) {
                    PDNode elseRegion = new PDNode();
                    elseRegion.setCode("ELSE");
                    elseRegion.setLineOfCode(test);
                    test++;
                    cdg.addVertex(elseRegion);
                    cdg.addEdge(new Edge<>(nowif, new CDEdge(CDEdge.Type.TRUE), elseRegion));
                    popCtrlDep(nowif);
                    nowelse = elseRegion;
                    pushCtrlDep(nowelse);
                }
                else
                {
                    popCtrlDep(nowif);
                    build =false;
                }
                if(ctx.else_tail().statement_list_terms()!=null) {
                    visit(ctx.else_tail().statement_list_terms());
                    expNode(ctx.else_tail().statement_list_terms());
                }
                popCtrlDep(nowelse);
                nowthen=null;
                nowelse=null;
            }
            nowif=null;
            popCtrlDep(ifNode);
            Done=true;
            return null;
        }
        @Override
        public PDNode visitFunction_definition(RubyParser.Function_definitionContext ctx)
        {
            init();
            PDNode entry = new PDNode();
            entry.setLineOfCode(ctx.getStart().getLine());
            String tmp=getOriginalCodeText(ctx.function_definition_header());
            tmp=tmp.replace("\r\n","");
            entry.setCode(tmp);
            if(nowclass.isEmpty())
                addNodeEdge(entry);
            else
            {
                cdg.addVertex(entry);
                cdg.addEdge(new Edge<>(nowclass.peek(), new CDEdge(CDEdge.Type.EPSILON), entry));
            }
            methodentry=entry;
            if(!classNames.isEmpty())
                entry.setProperty("class", classNames.peek());
            pushCtrlDep(entry);

            if(ctx.statement_list_terms().statement()!=null)
            {
                visit(ctx.statement_list_terms());
                expNode(ctx.statement_list_terms());
                if(RETURN.isEmpty())
                {
                    PDNode exp = new PDNode();
                    PDNode endmethod = new PDNode();
                    endmethod.setLineOfCode(0);
                    if(!expr.equals("")) {
                        exp.setLineOfCode(ctx.statement_list_terms().terms().getStart().getLine());
                        exp.setCode(expr);

                        addNodeEdge(exp);

                        endmethod.setCode("return " +expr);
                        cdg.addVertex(endmethod);
                        cdg.addEdge(new Edge<>(entry, new CDEdge(CDEdge.Type.EPSILON), endmethod));
                        expr="";
                    }
                    else {
                        endmethod.setCode("return last" );
                        cdg.addVertex(endmethod);
                        cdg.addEdge(new Edge<>(entry, new CDEdge(CDEdge.Type.EPSILON), endmethod));
                    }
                }
                else {
                        for(int i=0;i<RETURN.size();i++)
                            popCtrlDep(RETURN.pop());
                }
            }
            else {
                PDNode endmethod = new PDNode();
                endmethod.setLineOfCode(0);
                endmethod.setCode("return nil");
                cdg.addVertex(endmethod);
                cdg.addEdge(new Edge<>(entry, new CDEdge(CDEdge.Type.EPSILON), endmethod));
            }
            popCtrlDep(entry);
            methodentry=null;
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryBlockClassDef(RubyParser.PrimaryBlockClassDefContext ctx)
        {
            if(ctx.class_definition().class_header().cpath()!=null) {
                classNames.push(getOriginalCodeText(ctx.class_definition().class_header().cpath()));
                visit(ctx.class_definition());
                classNames.pop();
            }
            else {
                classNames.push(getOriginalCodeText(ctx.class_definition().class_header().identifier()));
                visit(ctx.class_definition());
                classNames.pop();
            }
            Done=true;
            return null;
        }
        @Override
        public PDNode visitNext(RubyParser.NextContext ctx)
        {
            PDNode brea=new PDNode();
            brea.setLineOfCode(ctx.getStart().getLine());
            brea.setCode("break");
            addNodeEdge(brea);
            PDNode region=new PDNode();
            region.setLineOfCode(0);
            region.setCode("BUILD-REGION = " + num);
            region.setProperty("isExit", Boolean.FALSE);
            region.setProperty("isJump", Boolean.TRUE);
            if(ctrlDeps.peek().getCode().equals("THEN"))
            {
                cdg.addVertex(region);
                if(nowelse!=null)
                {
                    cdg.addVertex(region);
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    num++;
                }
                else {
                    PDNode elseregion=new PDNode();
                    elseregion.setLineOfCode(test);
                    test++;
                    elseregion.setCode("ELSE");
                    cdg.addVertex(elseregion);
                    cdg.addEdge(new Edge<>(nowif, new CDEdge(type), elseregion));
                    nowelse=elseregion;
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    build=true;
                }
                num++;
            }
            else  if(ctrlDeps.peek().getCode().equals("ELSE"))
            {
                cdg.addVertex(region);
                cdg.addEdge(new Edge<>(nowthen, new CDEdge(CDEdge.Type.EPSILON), region));
                pushCtrlDep(region);
                num++;
            }
            NEXT.push(region);
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryStatementReturn(RubyParser.PrimaryStatementReturnContext ctx)
        {
            PDNode brea=new PDNode();
            brea.setLineOfCode(ctx.getStart().getLine());
            if(ctx.expr()==null)
                brea.setCode("return");
            else
            {
                brea.setCode("return "+getOriginalCodeText(ctx.expr()));
            }
            addNodeEdge(brea);
            PDNode region=new PDNode();
            region.setLineOfCode(0);
            region.setCode("BUILD-REGION = " + num);
            region.setProperty("isExit", Boolean.TRUE);
            region.setProperty("isJump", Boolean.FALSE);
            if(ctrlDeps.peek().getCode().equals("THEN"))
            {
                cdg.addVertex(region);
                if(nowelse!=null)
                {
                    cdg.addVertex(region);
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    num++;
                }
                else {
                    PDNode elseregion=new PDNode();
                    elseregion.setLineOfCode(test);
                    test++;
                    elseregion.setCode("ELSE");
                    cdg.addVertex(elseregion);
                    cdg.addEdge(new Edge<>(nowif, new CDEdge(type), elseregion));
                    nowelse=elseregion;
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    build=true;
                }
                num++;
            }
            else  if(ctrlDeps.peek().getCode().equals("ELSE"))
            {
                cdg.addVertex(region);
                cdg.addEdge(new Edge<>(nowthen, new CDEdge(CDEdge.Type.EPSILON), region));
                pushCtrlDep(region);
                num++;
            }

            RETURN.push(region);
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryStatementRaise(RubyParser.PrimaryStatementRaiseContext ctx)
        {
            PDNode brea=new PDNode();
            brea.setLineOfCode(ctx.getStart().getLine());
            brea.setCode("raise "+getOriginalCodeText(ctx.expr()));
            addNodeEdge(brea);
            PDNode region=new PDNode();
            region.setLineOfCode(0);
            region.setCode("BUILD-REGION = " + num);
            region.setProperty("isExit", Boolean.TRUE);
            region.setProperty("isJump", Boolean.FALSE);

            if(ctrlDeps.peek().getCode().equals("THEN"))
            {
                cdg.addVertex(region);
                if(nowelse!=null)
                {
                    cdg.addVertex(region);
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    num++;
                }
                else {
                    PDNode elseregion=new PDNode();
                    elseregion.setLineOfCode(test);
                    test++;
                    elseregion.setCode("ELSE");
                    cdg.addVertex(elseregion);
                    cdg.addEdge(new Edge<>(nowif, new CDEdge(type), elseregion));
                    nowelse=elseregion;
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    build=true;
                }
                num++;
            }
            else  if(ctrlDeps.peek().getCode().equals("ELSE"))
            {
                cdg.addVertex(region);
                cdg.addEdge(new Edge<>(nowthen, new CDEdge(CDEdge.Type.EPSILON), region));
                pushCtrlDep(region);
                num++;
            }
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryStatementBreak(RubyParser.PrimaryStatementBreakContext ctx)
        {
            PDNode brea=new PDNode();
            brea.setLineOfCode(ctx.getStart().getLine());
            brea.setCode("break");
            addNodeEdge(brea);
            PDNode region=new PDNode();
            region.setLineOfCode(0);
            region.setCode("BUILD-REGION = " + num);
            region.setProperty("isExit", Boolean.FALSE);
            region.setProperty("isJump", Boolean.TRUE);
            if(ctrlDeps.peek().getCode().equals("THEN"))
            {
                cdg.addVertex(region);
                if(nowelse!=null)
                {
                    cdg.addVertex(region);
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    num++;
                }
                else {
                    PDNode elseregion=new PDNode();
                    elseregion.setLineOfCode(test);
                    test++;
                    elseregion.setCode("ELSE");
                    cdg.addVertex(elseregion);
                    cdg.addEdge(new Edge<>(nowif, new CDEdge(type), elseregion));
                    nowelse=elseregion;
                    cdg.addEdge(new Edge<>(nowelse, new CDEdge(CDEdge.Type.EPSILON), region));
                    pushCtrlDep(region);
                    build=true;
                }
                num++;
            }
            else  if(ctrlDeps.peek().getCode().equals("ELSE"))
            {
                cdg.addVertex(region);
                cdg.addEdge(new Edge<>(nowthen, new CDEdge(CDEdge.Type.EPSILON), region));
                pushCtrlDep(region);
                num++;
            }

            BREAK.push(region);
            Done=true;
            return null;
        }
        @Override
        public PDNode visitPrimaryBeginBlock(RubyParser.PrimaryBeginBlockContext ctx)
        {
            PDNode begin=new PDNode();
            begin.setLineOfCode(ctx.getStart().getLine());
            begin.setCode("begin");
            cdg.addVertex(begin);
            if(methodentry==null)
                cdg.addEdge(new Edge<>(root, new CDEdge(CDEdge.Type.EPSILON), begin));
            else
                cdg.addEdge(new Edge<>(methodentry, new CDEdge(CDEdge.Type.EPSILON), begin));
            pushCtrlDep(begin);
            visit(ctx.statement_list_terms(0));
            expNode(ctx.statement_list_terms(0));
            popCtrlDep(begin);
            if(ctx.rescuestatement()!=null)
            {
                PDNode rescue=new PDNode();
                rescue.setLineOfCode(ctx.rescuestatement().getStart().getLine());
                if(ctx.rescuestatement().rescure_param()!=null)
                    rescue.setCode("rescue "+getOriginalCodeText(ctx.rescuestatement().rescure_param()));
                else
                    rescue.setCode("rescue");
                cdg.addVertex(rescue);
                cdg.addEdge(new Edge<>(begin, new CDEdge(CDEdge.Type.THROWS), rescue));
                pushCtrlDep(rescue);
                visit(ctx.rescuestatement().statement_list_terms());
                expNode(ctx.rescuestatement().statement_list_terms());
                popCtrlDep(rescue);
                if(ctx.rescuestatement().else_tail()!=null)
                {
                    PDNode els =new PDNode();
                    els.setLineOfCode(ctx.rescuestatement().else_tail().getStart().getLine());
                    els.setCode("else");
                    cdg.addVertex(els);
                    cdg.addEdge(new Edge<>(begin, new CDEdge(CDEdge.Type.NOT_THROWS), els));
                    pushCtrlDep(els);
                    visit(ctx.rescuestatement().else_tail().statement_list_terms());
                    expNode(ctx.rescuestatement().else_tail().statement_list_terms());
                    popCtrlDep(els);
                }
            }
            if(ctx.ensure()!=null)
            {
                PDNode ensure =new PDNode();
                ensure.setLineOfCode(ctx.ensure().getStart().getLine());
                ensure.setCode("ensure");
                cdg.addVertex(ensure);
                cdg.addEdge(new Edge<>(begin, new CDEdge(CDEdge.Type.EPSILON), ensure));
                pushCtrlDep(ensure);
                visit(ctx.statement_list_terms(1));
                expNode(ctx.statement_list_terms(1));
                popCtrlDep(ensure);
            }
            Done=true;
            return null;
        }
        @Override
        public PDNode visitEnd_block(RubyParser.End_blockContext ctx)
        {
            PDNode end =new PDNode();
            end.setLineOfCode(ctx.getStart().getLine());
            end.setCode("END");
            cdg.addVertex(end);
            cdg.addEdge(new Edge<>(root, new CDEdge(CDEdge.Type.EPSILON), end));
            pushCtrlDep(end);
            visit(ctx.statement_list_terms());
            expNode(ctx.statement_list_terms());
            popCtrlDep(end);
            Done=true;
            return null;
        }
        @Override
        public PDNode visitCompilation_unit(RubyParser.Compilation_unitContext ctx)
        {
            visitChildren(ctx);
            if(!expr.equals("")) {
                PDNode exp = new PDNode();
                exp.setLineOfCode(ctx.EOF().getSymbol().getLine());
                exp.setCode(expr);
                addNodeEdge(exp);
            }
            return null;
        }
        @Override

        public PDNode visitModule_definition(RubyParser.Module_definitionContext ctx)
        {
            init();
            PDNode entry = new PDNode();                         //构建类头节点
            entry.setLineOfCode(ctx.getStart().getLine());
            entry.setCode("module"+getOriginalCodeText(ctx.cpath()));
            if(nowclass.isEmpty())
                addNodeEdge(entry);
            else
            {
                cdg.addVertex(entry);
                cdg.addEdge(new Edge<>(nowclass.peek(), new CDEdge(CDEdge.Type.EPSILON), entry));
            }
            pushCtrlDep(entry);
            visit(ctx.statement_list_terms());
            expNode(ctx.statement_list_terms());
//            endclass.setLineOfCode(ctx.END().getSymbol().getLine());
//            endclass.setCode("endclass");
//            endclass.setProperty("class", classNames.peek());
//            cdg.addVertex(endclass);
//            addNodeEdge(endclass);
            popCtrlDep(entry);
            Done=true;
            return null;
        }
        @Override
        public PDNode visitClass_definition(RubyParser.Class_definitionContext ctx)
        {
            init();
            PDNode entry = new PDNode();                         //构建类头节点
            entry.setLineOfCode(ctx.getStart().getLine());
            entry.setCode(getOriginalCodeText(ctx.class_header()));

            entry.setProperty("class", classNames.peek());
            cdg.addVertex(entry);
            pushCtrlDep(entry);
            nowclass.push(entry);
            visit(ctx.statement_list_terms());
            expNode(ctx.statement_list_terms());

//            endclass.setLineOfCode(ctx.END().getSymbol().getLine());
//            endclass.setCode("endclass");
//            endclass.setProperty("class", classNames.peek());
//            cdg.addVertex(endclass);
//            addNodeEdge(endclass);
            popCtrlDep(entry);
            nowclass.pop();
            Done=true;

            return null;

        }

        @Override
        public PDNode visitStatement_list_terms(RubyParser.Statement_list_termsContext ctx)
        {

            visitChildren(ctx);
            if(!Done)
            {
                if(ctx.statement()!=null) {
                    expr += getOriginalCodeText(ctx.statement());
                    if (ctx.terms().term().isEmpty()) {
                        expr += " ";
                    } else {
                        PDNode exp = new PDNode();
                        exp.setLineOfCode(ctx.terms().getStart().getLine());
                        exp.setCode(expr);
                        expr = "";
                        addNodeEdge(exp);
                        Done=false;
                        return exp;
                    }
                }
            }
            else
                Done=false;
            return null;
        }
        private void addNodeEdge(PDNode node) {
            checkBuildFollowRegion();   //judge and add the region node
            cdg.addVertex(node);
            cdg.addEdge(new Edge<>(ctrlDeps.peek(), new CDEdge(CDEdge.Type.EPSILON), node));
        }
        public void expNode(RubyParser.Statement_list_termsContext ctx)
        {
            if(!expr.equals("")) {
               PDNode exp = new PDNode();
                exp.setLineOfCode(ctx.terms().getStart().getLine());
                exp.setCode(expr);
                addNodeEdge(exp);

                expr="";
            }
        }
        /**
         * Check if a follow-region must be created;
         * if so, create it and push it on the CTRL-dependence stack.
         */
        private void checkBuildFollowRegion() {
            Logger.debug("FOLLOWS = " + follows);
            Logger.debug("BUILD-REGION = " + buildRegion);
            if (buildRegion && follows) {
                PDNode followRegion = new PDNode();
                followRegion.setLineOfCode(0);
                followRegion.setCode("FOLLOW-" + regionCounter++);
                cdg.addVertex(followRegion);
                // check to see if there are any exit-jumps in the current chain
                followRegion.setProperty("isJump", Boolean.TRUE);
                for (PDNode dep: jumpDeps)
                    if ((Boolean) dep.getProperty("isExit")) {
                        followRegion.setProperty("isJump", Boolean.FALSE);
                        followRegion.setProperty("isExit", Boolean.TRUE);
                    }
                if ((Boolean) followRegion.getProperty("isJump"))
                    ++jmpCounter;
                // connect the follow-region
                if (Boolean.TRUE.equals(jumpDeps.peek().getProperty("isTry"))) {
                    PDNode jmpDep = jumpDeps.pop();
                    if (!cdg.containsVertex(jmpDep))
                        cdg.addVertex(jmpDep);
                    cdg.addEdge(new Edge<>(jmpDep, new CDEdge(CDEdge.Type.NOT_THROWS), followRegion));
                } else {
                    PDNode jmpDep = jumpDeps.pop();
                    if (!cdg.containsVertex(jmpDep))
                        cdg.addVertex(jmpDep);
                    cdg.addEdge(new Edge<>(jmpDep, new CDEdge(CDEdge.Type.EPSILON), followRegion));
                }
                // if the jump-chain is not empty, remove all non-exit jumps
                if (!jumpDeps.isEmpty()) {
                    for (Iterator<PDNode> itr = jumpDeps.iterator(); itr.hasNext(); ) {
                        PDNode dep = itr.next();
                        if (Boolean.FALSE.equals(dep.getProperty("isExit")))
                            itr.remove();
                    }
                }
                lastFollowDepth = 0;
                pushCtrlDep(followRegion);
            }
        }

        /**
         * Push given node to the control-dependency stack.
         */
        private void pushCtrlDep(PDNode dep) {
            ctrlDeps.push(dep);
            buildRegion = false;
        }

        /**
         * Push this loop block region to the control-dependency stack
         * and reset the jumps-counter for this loop-block.
         */
        private void pushLoopBlockDep(PDNode region) {
            pushCtrlDep(region);
            jmpCounts.push(jmpCounter);
            jmpCounter = 0;
        }

        /**
         * Pop out the last dependency off the stack and
         * set the 'buildRegion' flag if necessary.
         */
        private void popCtrlDep(PDNode dep) {
//            List<PDNode> list = new ArrayList<>(ctrlDeps);
//            int index = list.indexOf(dep);
//            for(;index>=0;index--)
//                ctrlDeps.pop();
            ctrlDeps.remove(dep); //ctrlDeps.pop();
            buildRegion = !jumpDeps.isEmpty();
        }

        /**
         * Pop out this loop-block region off the control stack
         * and also pop off all jump-dependencies of this block.
         */
        private void popLoopBlockDep(PDNode region) {
            for (Iterator<PDNode> itr = ctrlDeps.iterator(); jmpCounter > 0 && itr.hasNext(); ) {
                // NOTE: This iteration works correctly, even though ctrlDeps is a stack.
                //       This is due to the Deque implementation, which removes in LIFO.
                PDNode dep = itr.next();
                if (Boolean.TRUE.equals(dep.getProperty("isJump"))) {
                    itr.remove();
                    --jmpCounter;
                }
            }
            jmpCounter = jmpCounts.pop();
            lastFollowDepth = 0;
            popCtrlDep(region);
        }

        /**
         * transfor the with statement to String
         */
//        private String tranWithItem(ParserRuleContext ctx){
//            assert ctx instanceof RubyParser.With_itemContext;
//            String code = ((RubyParser.With_itemContext) ctx).test().getText();
//            if (((RubyParser.With_itemContext) ctx).AS() != null){
//                code = code + " as " + ((RubyParser.With_itemContext) ctx).expr().getText();
//            }
//            return code;
//        }

        /**
         * Get the original program text for the given parser-rule context.
         * This is required for preserving whitespaces.
         */
        private String getOriginalCodeText(ParserRuleContext ctx) {
            int start = ctx.start.getStartIndex();
            int stop = ctx.stop.getStopIndex();
            Interval interval = new Interval(start, stop);
            return ctx.start.getInputStream().getText(interval);
        }
    }
}
